let a = 10
function fn1 (b) {
  let a = 2
  function fn2 (c) {
    console.log(a + b + c)
  }
  return fn2
}
let fn3 = fn1(2)
fn1(3)
/*代码开始执行时，创建一个执行环境栈【ECStack】，用于存放执行上下文
在执行第一行代码前，需要先创建一个全局的执行上下文 EC(G）
  并且初始化一些预定义的对象，即GO全局对象
然后将这个全局执行上下文压入执行环境栈中*/

/**
代码开始从第一行开始执行，let a = 10
此时在当前上下文中，初始化一个变量对象VO，存放 a = 10
a是一个基本类型数值10，所以直接存放在栈内存当中
 */

/**
代码继续执行，function fn1(b) {}
这个地方由于fn1函数是一个引用类型，所以需要开辟一个新的堆空间进行存储，假设地址为add_fn1
在add_fn1中，存放着 function fn1(b) { ...... },  name: fn1;  length: 1
此时，在VO中，存放 fn1 = add_fn1[存放的是fn1函数的地址]
并且函数在创建中已经拥有自己的作用域 [[scope]] VO
*/

/**
代码继续执行，let fn3 = fn1(2) ，
此时 fn1()函数被调用，所以再开启一个执行上下文 EC(fn1)
在 EC(fn1)这个函数执行上下文中，需要做一些事情
首先确定 this 指向，当前是非严格形式下的函数对象，所以， this = window
然后初始化作用域链，先是自己的作用域，<fn1.ao>
往上查找是全局执行上下文 EC(G) 中的作用域<VO>
此时作用域链初始化完成，然后初始化自己的AO
存放 argument: { 0: 2 }; b = 2;
继续执行代码， let a = 2
这时，在自己的上下文中也有一个变量对象AO 存放 a = 2
a = 2 是数值类型直接存放在栈内存中
 */

/**
继续执行代码， function fn2(c) {}
fn2函数也是一个引用类型，所以再开辟一个新的堆空间进行存储，假设地址为add_fn2
在add_fn2中，同样也存放着 function fn2(c) { ...... }, name: fn2, length: 1
此时，在AO中，存放 fn2 = add_fn2[存放的是fn2函数的地址]
同样，拥有自己的作用域 [[scope]] foo.AO
 */

/*
代码继续执行， return fn2
这个时候全局执行上下文VO中就可以拿到这个add_fn2的存放地址 即 fn3 = add_fn2
这个时候由于fn1函数已经执行完毕，内存本应该释放
但是在全局执行上下文中，仍然对fn1函数中的fn2, 即 add_fn2有引用
形成了闭包，这个时候内存不能被释放掉
当前浏览器的机制就会把当前执行栈向下压，将下面的栈提上来执行
 */

/**
继续执行代码，fn3(3)
fn3就是add_fn2，即 函数fn2，调用这个函数fn2，会再开辟一个新的执行上下文 EC(fn2)
首先确定 this 指向，当前是非严格形式下的函数对象，所以， this = window
然后初始化作用域链，先是自己的作用域，<fn2.AO>
往上查找是 EC(fn2) 的作用域<fn1.AO>
再往上查找是全局执行上下文 EC(G) 的作用域<VO>
此时作用域链初始化完成，然后初始化自己的AO
存放 argument: { 0: 3 }; c = 3;
 */

/**
继续执行代码，console.log(a + b + c)
a在自己的作用域中是没有的，所以按照作用域链的顺序向上查找，在 EC(fn2)中找到a，所以 a = 2
b在自己的作用域中也是没有的，所以按照作用域链的顺序向上查找，在 EC(fn2)中找到b，所以 b = 2
c 在自己的作用直接有的，直接拿来用 c = 3
所以最终输出 2 + 2 + 3 的结果，7
 */

/**
 这时候我们的代码就全部执行完毕了
栈中的数据就交由JS主线程就行回收【出栈】，堆中的数据就有GC进行回收
 */